package com.atguigu.gmall.index.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.bean.ResponseVo;

import com.atguigu.gmall.index.aspect.GmallCache;
import com.atguigu.gmall.index.feign.GmallPmsClient;
import com.atguigu.gmall.index.utils.DistributedLock;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;


import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class IndexService {
    @Autowired
    GmallPmsClient gmallPmsClient;

    public List<CategoryEntity> queryLvl1Categories() {
        ResponseVo<List<CategoryEntity>> listResponseVo = gmallPmsClient.queryCategoriesByPid(0L);
        return  listResponseVo.getData();
    }
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedissonClient redissonClient;

    private static final String KEY_PREFIX="index:cates:";
    private static final String LOCK_PREFIX="index:cates:lock:";

    @GmallCache(prefix =KEY_PREFIX,timeout = 259200,random = 7000,lock =LOCK_PREFIX )
    public List<CategoryEntity> queryLvl2WithSubsByPid(Long pid) {
        // 为了防止缓存穿透，数据即使为空也缓存，只是缓存时间较短
        // 1.查询缓存，缓存命中直接返回
        String json = stringRedisTemplate.opsForValue().get(KEY_PREFIX + pid);
        if(StringUtils.isNotBlank(json)){
            return JSON.parseArray(json,CategoryEntity.class);
        }

        // 为了防止缓存击穿添加分布式锁
        RLock fairLock = redissonClient.getFairLock(LOCK_PREFIX+pid);
        fairLock.lock();//加锁
        
        try {
            // 在获取分布式锁的过程中，可能有其他请求已经把数据放入缓存，此时应该再次确认缓存中是否存在
            //双重确认机制
            String json2 = stringRedisTemplate.opsForValue().get(KEY_PREFIX + pid);
            if(StringUtils.isNotBlank(json2)){
                return JSON.parseArray(json2,CategoryEntity.class);
            }

            ResponseVo<List<CategoryEntity>> listResponseVo = gmallPmsClient.queryLvl2CatesWithSubsByPid(pid);
            List<CategoryEntity> categoryEntities = listResponseVo.getData();
            // 2.远程调用或者查询数据库，放入缓存
            if(CollectionUtils.isEmpty(categoryEntities)){
                // 为了防止缓存穿透，数据即使为空也缓存，只是缓存时间较短
                stringRedisTemplate.opsForValue().set(KEY_PREFIX + pid,JSON.toJSONString(categoryEntities),5, TimeUnit.SECONDS);
            }else {
                // 为了防止缓存雪崩，给缓存时间添加随机值
                stringRedisTemplate.opsForValue().set(KEY_PREFIX+ pid,JSON.toJSONString(categoryEntities),new Random().nextInt(30)+90, TimeUnit.DAYS);
            }
            return categoryEntities;
        } finally {
            fairLock.unlock();//解锁
        }

    }





    public void testLock() {
        //获取锁
        //设置过期时间防止死锁（客户端从redis中获取到锁之后，客户端服务器宕机。）
        //设置uuid防误删
        String uuid = UUID.randomUUID().toString();
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,3,TimeUnit.SECONDS);
        if(!flag){
            //重试
            try {
                //防止oom
                Thread.sleep(10);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            String numString = stringRedisTemplate.opsForValue().get("num");
            if(StringUtils.isBlank(numString)){
                return;
            }
            int num = Integer.parseInt(numString);
            stringRedisTemplate.opsForValue().set("num",String.valueOf(++num));
            //释放锁
            String script ="if(redis.call('get',KEYS[1])==ARGV[1]) then return redis.call('del',KEYS[1]) else return 0 end";
            stringRedisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList("lock"),uuid);
        }
    }

    @Autowired
    DistributedLock distributedLock;
    public void testLock2() {

        String uuid = UUID.randomUUID().toString();
        //加锁
        Boolean lock = distributedLock.tryLock("lock", uuid, 30);
        if(lock){
            String numString = stringRedisTemplate.opsForValue().get("num");
            if(StringUtils.isBlank(numString)){
                return;
            }
            int num = Integer.parseInt(numString);
            stringRedisTemplate.opsForValue().set("num",String.valueOf(++num));
            //释放锁
            distributedLock.unlock("lock", uuid);
        }
    }



    public void testLock3() {
        //获取锁
        RLock lock = redissonClient.getLock("lock");
        //加锁
        lock.lock();
            String numString = stringRedisTemplate.opsForValue().get("num");
            if(StringUtils.isBlank(numString)){
                return;
            }
            int num = Integer.parseInt(numString);
            stringRedisTemplate.opsForValue().set("num",String.valueOf(++num));
            //释放锁
        lock.unlock();
    }

}
